/*
This is part of Screenbar, which is based in part on the Litestep
shell source code.

Copyright (C) 2008-2009 Jim Babcock
Copyright (C) 1997-2009 The LiteStep Development Team

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#include "stdafx.h"

/////////////////////////////////////////////////////////////////////////////

const int snapRegion = 5;

void transferAxis(int *outMin, int *outMax,
                  int min, int max,
                  int sourceMin, int sourceMax,
                  int destMin, int destMax)
{
	// If starting outside the range, move it in
	if(min < sourceMin) {
		max += sourceMin - min;
		min = sourceMin;
	}
	if(max > sourceMax) {
		min = max(sourceMin, min-max+sourceMax);
		max = sourceMax;
	}
	
	// Full-span?
	if(abs(min-sourceMin) < snapRegion && abs(max-sourceMax) < snapRegion)
	{
		// Preserve the offset (positive or negative) on both ends
		*outMin = min + (destMin - sourceMin);
		*outMax = max + (destMax - sourceMax);
	}
	// Too large (leave no margins)
	else if(max-min >= destMax-destMin)
	{
		*outMin = destMin;
		*outMax = destMax;
	}
	// If close to a margin, preserve that margin and rescale the other
	else if(abs(min-sourceMin) < snapRegion)
	{
		// Lower margin is closer
		*outMin = min + destMin - sourceMin;
		*outMax = max + destMin - sourceMin;
	}
	else if(abs(max-sourceMax) < snapRegion)
	{
		// Upper margin is closer
		*outMin = min + destMax - sourceMax;
		*outMax = max + destMax - sourceMax;
	}
	else
	{
		// Rescale both margins
		int totalSrcMargin = (sourceMax - sourceMin) - (max - min);
		int totalDestMargin = (destMax - destMin) - (max - min);
		float scale = (float)totalDestMargin / (float)totalSrcMargin;
		
		*outMin = destMin + (min-sourceMin)*scale;
		*outMax = *outMin + (max - min);
	}
}

void DeskSlot::transferFrom(Rect &windowRect, DeskSlot *prev) const
{
	Rect sourceRect = prev->getMaximizeArea();
	Rect destRect = getMaximizeArea();
	
	int left=-1, right=-1, top=-1, bottom=-1;
	transferAxis(&left, &right,  windowRect.left, windowRect.getRight(),  sourceRect.left, sourceRect.getRight(),  destRect.left, destRect.getRight());
	transferAxis(&top,  &bottom, windowRect.top,  windowRect.getBottom(), sourceRect.top,  sourceRect.getBottom(), destRect.top,  destRect.getBottom());
	
	windowRect = Rect(left, top, right-left, bottom-top);
}

void DeskSlot::storeRect(Rect &rect) const
{
	Point topLeft = getBoundingRect().topLeft();
	rect.left += topLeft.x;
	rect.top += topLeft.y;
}

void DeskSlot::unstoreRect(Rect &rect) const
{
	Point topLeft = getBoundingRect().topLeft();
	rect.left -= topLeft.x;
	rect.top -= topLeft.y;
}

void DeskSlot::storePoint(POINT &point) const
{
	Point topLeft = getBoundingRect().topLeft();
	point.x += topLeft.x;
	point.y += topLeft.y;
}

void DeskSlot::unstorePoint(POINT &point) const
{
	Point topLeft = getBoundingRect().topLeft();
	point.x -= topLeft.x;
	point.y -= topLeft.y;
}

/////////////////////////////////////////////////////////////////////////////

StorageArea::StorageArea(Rect &rect)
{
	this->x = rect.left;
	this->y = rect.top;
	
	this->owner = NULL;
	this->index = 0;
}

StorageArea::StorageArea(StorageManager *owner, int index, int x, int y)
	:owner(owner), index(index), x(x), y(y)
{
	screenBoundingRect = screenMaximizeRect = Rect(x, y, SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN);
}

StorageArea::~StorageArea()
{
}

Point StorageArea::getTopLeft() const
{
	return Point(x + GetSystemMetrics(SM_XVIRTUALSCREEN), y + GetSystemMetrics(SM_YVIRTUALSCREEN));
}

void StorageArea::setArea(Rect boundingRect, Rect maximizeArea)
{
	screenBoundingRect = boundingRect;
	screenMaximizeRect = maximizeArea;
}

Rect StorageArea::getBoundingRect() const
{
	Point topLeft = getTopLeft();
	return Rect(screenBoundingRect.left + topLeft.x, screenBoundingRect.top + topLeft.y,
		screenBoundingRect.width, screenBoundingRect.height);
}

Rect StorageArea::getMaximizeArea() const
{
	Point topLeft = getTopLeft();
	return Rect(screenMaximizeRect.left + topLeft.x, screenMaximizeRect.top + topLeft.y,
		screenMaximizeRect.width, screenMaximizeRect.height);
}

void StorageArea::release()
{
	owner->releaseStorageArea(this);
}

/////////////////////////////////////////////////////////////////////////////

StorageManager::StorageManager()
{
	maxArea = 0;
	spacingX = GetSystemMetrics(SM_CXVIRTUALSCREEN)*2;
	spacingY = GetSystemMetrics(SM_CYVIRTUALSCREEN)*2;
}

StorageManager::~StorageManager()
{
}

StorageArea *StorageManager::getStorageArea()
{
	if(!unusedAreas.size())
		createArea();
	
	set<int>::iterator unusedAreaIt = unusedAreas.begin();
	int index = *unusedAreaIt;
	unusedAreas.erase(unusedAreaIt);
	return areas[index];
}

void StorageManager::releaseStorageArea(StorageArea *area)
{
	unusedAreas.insert(area->index);
}

void StorageManager::createArea()
{
	int index = areas.size();
	int x = spacingX * (index+1);
	int y = 0;
	
	// If there are tons of VWMs, we might get a position that's long enough to
	// overflow a signed short, and that causes bad things. Keep a nice big
	// safety margin between the rightmost offscreen-storage area and
	// SSHORT_MAX.
	const int rightSideLimit = 31000 - GetSystemMetrics(SM_CXVIRTUALSCREEN)*2;
	while(x >= rightSideLimit) {
		x -= rightSideLimit;
		y += spacingY;
	}
	
	StorageArea *area = new StorageArea(this, index, x, y);
	areas.push_back(area);
	unusedAreas.insert(index);
}
